function mxSession(model, urlInit, urlPoll, urlNotify)
{
	this.model = model;
	this.urlInit = urlInit;
	this.urlPoll = urlPoll;
	this.urlNotify = urlNotify;

	if (model != null)
	{
		this.codec = new mxCodec();
		this.codec.lookup = function(id)
		{
			return model.getCell(id);
		};
	}

	var self = this;
	model.addListener(mxEvent.NOTIFY, function(sender, evt)
	{
		var changes = evt.getArgAt(0);

		if (changes != null && self.debug || (self.connected && !self.suspended))
		{
			self.notify(self.encodeChanges(changes));
		}
	});
};
mxSession.prototype = new mxEventSource();
mxSession.prototype.constructor = mxSession;
mxSession.prototype.model = null;
mxSession.prototype.urlInit = null;
mxSession.prototype.urlPoll = null;
mxSession.prototype.urlNotify = null;
mxSession.prototype.codec = null;
mxSession.prototype.linefeed = '\n';
mxSession.prototype.escapePostData = true;
mxSession.prototype.significantRemoteChanges = true;
mxSession.prototype.sent = 0;
mxSession.prototype.received = 0;
mxSession.prototype.debug = false;
mxSession.prototype.connected = false;
mxSession.prototype.suspended = false;
mxSession.prototype.polling = false;
mxSession.prototype.start = function()
{
	if (this.debug)
	{
		this.connected = true;
		this.fireEvent(mxEvent.CONNECT);
	}
	else if (!this.connected)
	{
		var self = this;
		this.get(this.urlInit, function(req)
		{
			self.connected = true;
			self.fireEvent(mxEvent.CONNECT);
			self.poll();
		});
	}
};
mxSession.prototype.suspend = function()
{
	if (this.connected && !this.suspended)
	{
		this.suspended = true;
		this.fireEvent(mxEvent.SUSPEND);
	}
};
mxSession.prototype.resume = function(type, attr, value)
{
	if (this.connected && this.suspended)
	{
		this.suspended = false;
		this.fireEvent(mxEvent.RESUME);

		if (!this.polling)
		{
			this.poll();
		}
	}
};
mxSession.prototype.stop = function(reason)
{
	if (this.connected)
	{
		this.connected = false;
	}
	this.fireEvent(mxEvent.DISCONNECT, new mxEventObject([reason]));
};
mxSession.prototype.poll = function()
{
	if (this.connected && !this.suspended && this.urlPoll != null)
	{
		this.polling = true;
		var self = this;
		this.get(this.urlPoll, function()
		{
			self.poll()
		});
	}
	else
	{
		this.polling = false;
	}
};
mxSession.prototype.notify = function(xml, onLoad, onError)
{
	if (xml != null && xml.length > 0)
	{
		if (this.urlNotify != null)
		{
			if (this.debug)
			{
				mxLog.show();
				mxLog.debug('mxSession.notify: ' + this.urlNotify + ' xml=' + xml);
			}
			else
			{
				if (this.escapePostData)
				{
					xml = encodeURIComponent(xml);
				}
				mxUtils.post(this.urlNotify, 'xml=' + xml, onLoad, onError);
			}
		}
		this.sent += xml.length;
		this.fireEvent(mxEvent.NOTIFY, new mxEventObject(
		[
			this.urlNotify,
			xml
		]));
	}
};

mxSession.prototype.get = function(url, onLoad, onError)
{
	if (typeof (mxUtils) != 'undefined')
	{
		var self = this;
		var onErrorWrapper = function(ex)
		{
			if (onError != null)
			{
				onError(ex);
			}
			else
			{
				self.stop(ex);
			}
		};

		var req = mxUtils.get(url, function(req)
		{
			if (typeof (mxUtils) != 'undefined')
			{
				try
				{
					if (req.isReady() && req.getStatus() != 404)
					{
						self.received += req.getText().length;
						self.fireEvent(mxEvent.GET, new mxEventObject(
						[
							url,
							req
						]));

						if (self.isValidResponse(req))
						{
							if (req.getText().length > 0)
							{
								var node = req.getDocumentElement();

								if (node == null)
								{
									onErrorWrapper('Invalid response: ' + req.getText());
								}
								else
								{
									self.receive(node);
								}
							}

							if (onLoad != null)
							{
								onLoad(req);
							}
						}
					}
					else
					{
						onErrorWrapper('Response not ready');
					}
				}
				catch (ex)
				{
					onErrorWrapper(ex);
					throw ex;
				}
			}
		}, function(req)
		{
			onErrorWrapper('Transmission error');
		});
	}
};
mxSession.prototype.isValidResponse = function(req)
{
	return req.getText().indexOf('<?php')<0;
};

mxSession.prototype.encodeChanges = function(changes)
{
	var xml = '';

	for (var i = 0; i < changes.length; i++)
	{
		var node = this.codec.encode(changes[i]);
		xml += mxUtils.getXml(node, this.linefeed);
	}
	return xml;
};
mxSession.prototype.receive = function(node)
{
	if (node != null && node.nodeType == mxConstants.NODETYPE_ELEMENT)
	{
		var name = node.nodeName.toLowerCase();

		if (name == 'state')
		{
			var tmp = node.firstChild;

			while (tmp != null)
			{
				this.receive(tmp);
				tmp = tmp.nextSibling;
			}

			var sid = node.getAttribute('namespace');
			this.model.prefix = sid + '-';
		}
		else if (name == 'delta')
		{
			var changes = this.decodeChanges(node);

			if (changes.length > 0)
			{
				var edit = this.createUndoableEdit(changes);
				this.model.fireEvent(mxEvent.UNDO, new mxEventObject([edit]));
				this.model.fireEvent(mxEvent.CHANGE, new mxEventObject([changes]));
				this.fireEvent(mxEvent.FIRED, new mxEventObject([changes]));
			}
		}
		this.fireEvent(mxEvent.RECEIVE, new mxEventObject([node]));
	}
};
mxSession.prototype.createUndoableEdit = function(changes)
{
	var edit = new mxUndoableEdit(this.model, this.significantRemoteChanges);
	edit.changes = changes;
	edit.notify = function()
	{
		edit.source.fireEvent(mxEvent.CHANGE, new mxEventObject([edit.changes]));
		edit.source.fireEvent(mxEvent.NOTIFY, new mxEventObject([edit.changes]));
	}
	return edit;
};
mxSession.prototype.decodeChanges = function(node)
{
	this.codec.document = node.ownerDocument;
	var changes = [];
	node = node.firstChild;

	while (node != null)
	{
		if (node.nodeType == mxConstants.NODETYPE_ELEMENT)
		{

			var change = null;

			if (node.nodeName == 'mxRootChange')
			{
				var codec = new mxCodec(node.ownerDocument);
				change = codec.decode(node);
			}
			else
			{
				change = this.codec.decode(node);
			}

			if (change != null)
			{
				change.model = this.model;
				change.execute();
				changes.push(change);
			}
		}
		node = node.nextSibling;
	}
	return changes;
};

function mxUndoableEdit(source, significant)
{
	this.source = source;
	this.changes = [];
	this.significant = (significant != null) ? significant : true;
};
mxUndoableEdit.prototype.source = null;
mxUndoableEdit.prototype.changes = null;
mxUndoableEdit.prototype.significant = null;
mxUndoableEdit.prototype.undone = false;
mxUndoableEdit.prototype.redone = false;
mxUndoableEdit.prototype.isEmpty = function()
{
	return this.changes.length == 0;
}
mxUndoableEdit.prototype.isSignificant = function()
{
	return this.significant;
};
mxUndoableEdit.prototype.add = function(change)
{
	this.changes.push(change);
};
mxUndoableEdit.prototype.notify = function()
{
};

mxUndoableEdit.prototype.die = function()
{
};

mxUndoableEdit.prototype.undo = function()
{
	if (!this.undone)
	{
		var count = this.changes.length;

		for (var i = count - 1; i >= 0; i--)
		{
			var change = this.changes[i];

			if (change.execute != null)
			{
				change.execute();
			}
			else if (change.undo != null)
			{
				change.undo();
			}
		}
		this.undone = true;
		this.redone = false;
	}
	this.notify();
};
mxUndoableEdit.prototype.redo = function()
{
	if (!this.redone)
	{
		var count = this.changes.length;

		for (var i = 0; i < count; i++)
		{
			var change = this.changes[i];

			if (change.execute != null)
			{
				change.execute();
			}
			else if (change.redo != null)
			{
				change.redo();
			}
		}
		this.undone = false;
		this.redone = true;
	}
	this.notify();
};

function mxUndoManager(size)
{
	this.size = size || 100;
	this.reset();
};
mxUndoManager.prototype = new mxEventSource();
mxUndoManager.prototype.constructor = mxUndoManager;
mxUndoManager.prototype.size = null;
mxUndoManager.prototype.history = null;
mxUndoManager.prototype.indexOfNextAdd = 0;
mxUndoManager.prototype.isEmpty = function()
{
	return this.history.length == 0;
};
mxUndoManager.prototype.reset = function()
{
	this.history = [];
	this.indexOfNextAdd = 0;
	this.fireEvent(mxEvent.RESET, new mxEventObject());
};
mxUndoManager.prototype.canUndo = function()
{
	return this.indexOfNextAdd > 0;
};
mxUndoManager.prototype.undo = function()
{
	while (this.indexOfNextAdd > 0)
	{
		var edit = this.history[--this.indexOfNextAdd];
		edit.undo();

		if (edit.isSignificant())
		{
			this.fireEvent(mxEvent.UNDO, new mxEventObject([edit]));
			break;
		}
	}
};
mxUndoManager.prototype.canRedo = function()
{
	return this.indexOfNextAdd < this.history.length;
};
mxUndoManager.prototype.redo = function()
{
	var n = this.history.length;

	while (this.indexOfNextAdd < n)
	{
		var edit = this.history[this.indexOfNextAdd++];
		edit.redo();

		if (edit.isSignificant())
		{
			this.fireEvent(mxEvent.REDO, new mxEventObject([edit]));
			break;
		}
	}
};
mxUndoManager.prototype.undoableEditHappened = function(undoableEdit)
{
	this.trim();

	if (this.size > 0 && this.size == this.history.length)
	{
		this.history.shift();
	}
	this.history.push(undoableEdit);
	this.indexOfNextAdd = this.history.length;
	this.fireEvent(mxEvent.ADD, new mxEventObject([undoableEdit]));
};
mxUndoManager.prototype.trim = function()
{
	if (this.history.length > this.indexOfNextAdd)
	{
		var edits = this.history.splice(this.indexOfNextAdd, this.history.length - this.indexOfNextAdd);

		for (var i = 0; i < edits.length; i++)
		{
			edits[i].die();
		}
	}
};

function mxAutoSaveManager(graph)
{
	var self = this;

	this.changeHandler = function(sender, evt)
	{
		if (self.isEnabled())
		{
			self.graphModelChanged(evt.getArgAt(0));
		}
	};
	this.setGraph(graph);
};
mxAutoSaveManager.prototype = new mxEventSource();
mxAutoSaveManager.prototype.constructor = mxAutoSaveManager;
mxAutoSaveManager.prototype.graph = null;
mxAutoSaveManager.prototype.autoSaveDelay = 10;
mxAutoSaveManager.prototype.autoSaveThrottle = 2;
mxAutoSaveManager.prototype.autoSaveThreshold = 5;
mxAutoSaveManager.prototype.ignoredChanges = 0;
mxAutoSaveManager.prototype.lastSnapshot = 0;
mxAutoSaveManager.prototype.enabled = true;
mxAutoSaveManager.prototype.changeHandler = null;
mxAutoSaveManager.prototype.isEnabled = function()
{
	return this.enabled;
};
mxAutoSaveManager.prototype.setEnabled = function(value)
{
	this.enabled = value;
};
mxAutoSaveManager.prototype.setGraph = function(graph)
{
	if (this.graph != null)
	{
		this.graph.getModel().removeListener(this.changeHandler);
	}
	this.graph = graph;

	if (this.graph != null)
	{
		this.graph.getModel().addListener(mxEvent.CHANGE, this.changeHandler);
	}
};
mxAutoSaveManager.prototype.save = function()
{
};

mxAutoSaveManager.prototype.graphModelChanged = function(changes)
{
	var now = new Date().getTime();
	var dt = (now - this.lastSnapshot) / 1000;

	if (dt > this.autoSaveDelay || (this.ignoredChanges >= this.autoSaveThreshold && dt > this.autoSaveThrottle))
	{
		this.save();
		this.reset();
	}
	else
	{
		this.ignoredChanges++;
	}
};
mxAutoSaveManager.prototype.reset = function()
{
	this.lastSnapshot = new Date().getTime();
	this.ignoredChanges = 0;
};
mxAutoSaveManager.prototype.destroy = function()
{
	this.setGraph(null);
};